# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
## NPSolitaire:
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## from PySolFC code:
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Demo mix-in for move hint support. Handles hints and demos for game.
"""
__docformat__ = "restructuredtext en"

import abc
import logging

LOGGER = logging.getLogger('PySolFC.GameLib.DemoMixin')

from . import state
from .data import GAME_DB
from ..settings import CONF_OPT
from ..statdb import STAT_DB
from ..viewlib.dialog import Dialog


class DemoMove(object):

    __hint = None

    def __init__(self, game):
        """
        Init Demo
        """
        self.__game = game

    def __call__(self):
        """
        Demo move based on level 2 move hint. Return game finish.

        .. \move() -> bool
        """
        game = self.__game
        if not game.is_demo:
            return False
        if game.move_index > 2000:  # Stop on moves > 2000.
            return True
        hint = self.__hint
        # Deal cards to the waste (unless there is a forced move)
        if hint and not hint.forced_move:
            if game._auto_deal():
                return False  # continue
        # Get new hint  display a hint
        self.__hint = hint = game.move_hint.get(level=2, taken_hint=hint)
        if not hint:
            return True  # finish no more hints
        # Deal cards according to given hint
        if hint.ncards == 0:
            if game.talon_deal() == 0 or game.move_dupe > 0:
                return True  # finish talon empty or Stop duplicate move
        # Flip cards according to given hint
        elif hint.from_stack == hint.to_stack:
            game.flip(hint.from_stack)
        # Play cards according to given hint
        else:
            game.move(hint.ncards, hint.from_stack, hint.to_stack)
        return False  # continue


class GameDemo(object):
    """
    Game demo mix-ins class.

    Provide methods for user hints and demo moves.
    """
    __metaclass__ = abc.ABCMeta
    __demo = None

    # Abstract properties
    top = canvas = _move = images = nextgame = move_hints = None
    preview = False

    @abc.abstractmethod
    def log(self):
        """
        Stub to log current move. Satisfied with move module in library.

        .. log() -> None
        """
        pass

    @abc.abstractmethod
    def _auto_deal(self, sound=True):
        """
        Stub to auto deal card to waste. Satisfied with move module in library.

        .. \_auto_deal([sound:bool=True]) -> bool
        """
        pass

    @abc.abstractmethod
    def new(self, random=None, restart=False, autoplay=True):
        """
        Stub to start game. Satisfied with module in library.

        .. new([random:Random=None, restart:bool=False,
             autoplay:bool=True]) -> None
        """
        pass

    @abc.abstractmethod
    def quit(self, id_=0, seed=None, data=None, mode=None):
        """
        Stub to quit game to outer mainloop. Satisfied with module in library.

        .. quit([id_:int=0, seed:Random, data:GameData, mode:str]) -> None
        """
        pass

    @abc.abstractmethod
    def _timer_stop(self):
        """
        Stub to stop timer for current game. Satisfied with module in library.

        .. \_timer_stop() -> None.
        """
        pass

    def nextgame_flags(self, id_, random_class=None):
        """
        Stub to get change between games. Satisfied with module in library

        .. nextgame_flags(id_:int[, random_class:Random=None]) -> int
        """
        flags = 0
        ancestor = super(GameDemo, self)
        if hasattr(ancestor, 'nextgame_flags'):
            flags = ancestor.nextgame_flags(id_, random_class=random_class)
        return flags

    @abc.abstractproperty
    def is_complete(self):
        """
        Stub to check for game success. Satisfied with module in library.

        .. is_complete -> bool
        """
        pass

    @property
    def can_play_sound(self):
        """
        Can play sound for moves? Default when not S_DEAL or is demo mode.

        .. can_play_sound -> bool
        """
        play_sound = True

        ancestor = super(GameDemo, self)
        if hasattr(ancestor, 'can_play_sound'):
            play_sound = ancestor.can_play_sound

        return not self.is_demo and play_sound

    @abc.abstractproperty
    def move_index(self):
        """
        Stub to return move index. Satisfied with move module in library.

        .. move_index -> int
        """
        pass

    @abc.abstractproperty
    def move_demo(self):
        """
        Stub to return player moves. Satisfied with move module in library.

        .. move_player -> int
        """
        pass

    @abc.abstractproperty
    def move_player(self):
        """
        Stub to return player moves. Satisfied with move module in library.

        .. move_player -> int
        """
        pass

    @property
    def is_demo(self):
        """
        Return current demo state. Satisfied with move module in library.

        is_demo -> bool
        """
        return self.__demo is not None

    def reset(self, restart=False):
        """
        Reset or restart current game.

        Augment to init move hint object and reset demo_mode. Call next method
        in order.

        .. reset([restart:bool=False]) -> None

        *Variables*:
          move_hints : {MoveHint}
            Set with current game and _hint_ for calculation of possible moves.
        """
        ancestor = super(GameDemo, self)
        if hasattr(ancestor, 'reset'):
            ancestor.reset(restart)
        self.__demo = None

    @state.not_state('is_preview')
    def demo_start(self, mixed=True):
        """
        Start demo. Manage demo logo above canvas. Set callback for demo event.

        .. demo_start([mixed:bool=True]) -> None
        """
        if not self.top or self.is_demo:
            return
        self.__demo = DemoMove(self)
        self.__mixed = bool(mixed)
        self.__moves = self.move_demo
        self.move_hint.reset()
        if CONF_OPT['demo_logo']:
            self.canvas.set_overlay('demo')
        self.top.after_idle(self.__demo_event)  # set first move

    def demo_stop(self):
        """
        Stop demo. Manage demo logo above canvas. Reset demo, update menus.

        .. demo_stop() -> None
        """
        if not self.is_demo:
            return
        self.__demo = None
        self.canvas.set_overlay()  # Clear overlay
        if not self.is_preview:
            self.top.event_generate('<<update_menus>>')

    def __demo_done(self, status, result):
        """
        Set next game for demo or stop.

        .. \__demo_event() -> None
        """
        is_mapped = self.top.winfo_ismapped()
        self.demo_stop()
        # 1) Increase splash counter with time-out if last demo moved.
        if status == False and self.move_demo - self.__moves > 0:
            self.nextgame['counter']['demo'] += 1
            if self.nextgame['counter']['demo'] % 3 == 0 and is_mapped():
                dlg = Dialog.about(timeout=10000)
                status, result = dlg.state, dlg.result
        if status == True and result == 0x20:    # select restart occured
            self.new(restart=True)
        elif status == False or result == 0x10:  # select new or timeout occured
            if self.__mixed:                     # Mixed mode: start diff game.
                import random
                id_ = self['id']
                while id_ == self['id']:
                    id_ = random.choice(GAME_DB.sort('name'))
                self.quit(id_=id_, mode='demo')
            else:                                # Normal mode: start new game.
                self.new()
                self.demo_start()

    def __demo_event(self):
        """
        Handle a demo event. Call demo move method. Either show a finish demo
        dialog or set next move event based on new state.

        .. \__demo_event() -> None
        """
        if self.__demo is None:
            return
        top = self.top
        finished = self.__demo()
        self.log()
        completed = self.is_complete
        top.update_idletasks()
        # Check game completed or finished.
        if (completed or finished) and top.winfo_ismapped():
            LOGGER.debug('done:  %s/%s', self.move_demo, self.move_index)
            self._timer_stop()
            self.canvas.set_overlay()  # clear overlay
            # Update demo stats for game.
            if self.move_player == 0 and CONF_OPT['update_stats']:
                STAT_DB.update(None, self, status=completed)
            top.event_generate('<<update_menus>>')
            dlg = Dialog.finish_demo(self.move_player, completed)
            self.__demo_done(dlg.state, dlg.result)
        else:
            top.update()
            top.after_idle(self.__demo_event)  # set next move